home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2000 March / maximum-cd-2000-03.iso / Quake3 Game Source / Q3AGameSource.exe / Main / g_combat.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-01-18  |  18.8 KB  |  770 lines

  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3. // g_combat.c
  4.  
  5. #include "g_local.h"
  6.  
  7.  
  8. /*
  9. ============
  10. AddScore
  11.  
  12. Adds score to both the client and his team
  13. ============
  14. */
  15. void AddScore( gentity_t *ent, int score ) {
  16.     if ( !ent->client ) {
  17.         return;
  18.     }
  19.     // no scoring during pre-match warmup
  20.     if ( level.warmupTime ) {
  21.         return;
  22.     }
  23.     ent->client->ps.persistant[PERS_SCORE] += score;
  24.     if (g_gametype.integer == GT_TEAM)
  25.         level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
  26.     CalculateRanks();
  27. }
  28.  
  29. /*
  30. =================
  31. TossClientItems
  32.  
  33. Toss the weapon and powerups for the killed player
  34. =================
  35. */
  36. void TossClientItems( gentity_t *self ) {
  37.     gitem_t        *item;
  38.     int            weapon;
  39.     float        angle;
  40.     int            i;
  41.     gentity_t    *drop;
  42.  
  43.     // drop the weapon if not a gauntlet or machinegun
  44.     weapon = self->s.weapon;
  45.  
  46.     // make a special check to see if they are changing to a new
  47.     // weapon that isn't the mg or gauntlet.  Without this, a client
  48.     // can pick up a weapon, be killed, and not drop the weapon because
  49.     // their weapon change hasn't completed yet and they are still holding the MG.
  50.     if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
  51.         if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
  52.             weapon = self->client->pers.cmd.weapon;
  53.         }
  54.         if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
  55.             weapon = WP_NONE;
  56.         }
  57.     }
  58.  
  59.     if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK && 
  60.         self->client->ps.ammo[ weapon ] ) {
  61.         // find the item type for this weapon
  62.         item = BG_FindItemForWeapon( weapon );
  63.  
  64.         // spawn the item
  65.         Drop_Item( self, item, 0 );
  66.     }
  67.  
  68.     // drop all the powerups if not in teamplay
  69.     if ( g_gametype.integer != GT_TEAM ) {
  70.         angle = 45;
  71.         for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
  72.             if ( self->client->ps.powerups[ i ] > level.time ) {
  73.                 item = BG_FindItemForPowerup( i );
  74.                 if ( !item ) {
  75.                     continue;
  76.                 }
  77.                 drop = Drop_Item( self, item, angle );
  78.                 // decide how many seconds it has left
  79.                 drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
  80.                 if ( drop->count < 1 ) {
  81.                     drop->count = 1;
  82.                 }
  83.                 angle += 45;
  84.             }
  85.         }
  86.     }
  87. }
  88.  
  89.  
  90. /*
  91. ==================
  92. LookAtKiller
  93. ==================
  94. */
  95. void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
  96.     vec3_t        dir;
  97.     vec3_t        angles;
  98.  
  99.     if ( attacker && attacker != self ) {
  100.         VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
  101.     } else if ( inflictor && inflictor != self ) {
  102.         VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
  103.     } else {
  104.         self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
  105.         return;
  106.     }
  107.  
  108.     self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
  109.  
  110.     angles[YAW] = vectoyaw ( dir );
  111.     angles[PITCH] = 0; 
  112.     angles[ROLL] = 0;
  113. }
  114.  
  115. /*
  116. ==================
  117. GibEntity
  118. ==================
  119. */
  120. void GibEntity( gentity_t *self, int killer ) {
  121.     G_AddEvent( self, EV_GIB_PLAYER, killer );
  122.     self->takedamage = qfalse;
  123.     self->s.eType = ET_INVISIBLE;
  124.     self->r.contents = 0;
  125. }
  126.  
  127. /*
  128. ==================
  129. body_die
  130. ==================
  131. */
  132. void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
  133.     if ( self->health > GIB_HEALTH ) {
  134.         return;
  135.     }
  136.     if ( !g_blood.integer ) {
  137.         self->health = GIB_HEALTH+1;
  138.         return;
  139.     }
  140.  
  141.     GibEntity( self, 0 );
  142. }
  143.  
  144.  
  145. // these are just for logging, the client prints its own messages
  146. char    *modNames[] = {
  147.     "MOD_UNKNOWN",
  148.     "MOD_SHOTGUN",
  149.     "MOD_GAUNTLET",
  150.     "MOD_MACHINEGUN",
  151.     "MOD_GRENADE",
  152.     "MOD_GRENADE_SPLASH",
  153.     "MOD_ROCKET",
  154.     "MOD_ROCKET_SPLASH",
  155.     "MOD_PLASMA",
  156.     "MOD_PLASMA_SPLASH",
  157.     "MOD_RAILGUN",
  158.     "MOD_LIGHTNING",
  159.     "MOD_BFG",
  160.     "MOD_BFG_SPLASH",
  161.     "MOD_WATER",
  162.     "MOD_SLIME",
  163.     "MOD_LAVA",
  164.     "MOD_CRUSH",
  165.     "MOD_TELEFRAG",
  166.     "MOD_FALLING",
  167.     "MOD_SUICIDE",
  168.     "MOD_TARGET_LASER",
  169.     "MOD_TRIGGER_HURT",
  170.     "MOD_GRAPPLE"
  171. };
  172.  
  173. /*
  174. ==================
  175. player_die
  176. ==================
  177. */
  178. void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
  179.     gentity_t    *ent;
  180.     int            anim;
  181.     int            contents;
  182.     int            killer;
  183.     int            i;
  184.     char        *killerName, *obit;
  185.  
  186.     if ( self->client->ps.pm_type == PM_DEAD ) {
  187.         return;
  188.     }
  189.  
  190.     if ( level.intermissiontime ) {
  191.         return;
  192.     }
  193.  
  194.     if (self->client && self->client->hook)
  195.         Weapon_HookFree(self->client->hook);
  196.  
  197.     self->client->ps.pm_type = PM_DEAD;
  198.  
  199.     if ( attacker ) {
  200.         killer = attacker->s.number;
  201.         if ( attacker->client ) {
  202.             killerName = attacker->client->pers.netname;
  203.         } else {
  204.             killerName = "<non-client>";
  205.         }
  206.     } else {
  207.         killer = ENTITYNUM_WORLD;
  208.         killerName = "<world>";
  209.     }
  210.  
  211.     if ( killer < 0 || killer >= MAX_CLIENTS ) {
  212.         killer = ENTITYNUM_WORLD;
  213.         killerName = "<world>";
  214.     }
  215.  
  216.     if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
  217.         obit = "<bad obituary>";
  218.     } else {
  219.         obit = modNames[ meansOfDeath ];
  220.     }
  221.  
  222.     G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", 
  223.         killer, self->s.number, meansOfDeath, killerName, 
  224.         self->client->pers.netname, obit );
  225.  
  226.     // broadcast the death event to everyone
  227.     ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
  228.     ent->s.eventParm = meansOfDeath;
  229.     ent->s.otherEntityNum = self->s.number;
  230.     ent->s.otherEntityNum2 = killer;
  231.     ent->r.svFlags = SVF_BROADCAST;    // send to everyone
  232.  
  233.     self->enemy = attacker;
  234.  
  235.     self->client->ps.persistant[PERS_KILLED]++;
  236.  
  237.     if (attacker && attacker->client) {
  238.         if ( attacker == self || OnSameTeam (self, attacker ) ) {
  239.             AddScore( attacker, -1 );
  240.         } else {
  241.             AddScore( attacker, 1 );
  242.  
  243.             if( meansOfDeath == MOD_GAUNTLET ) {
  244.                 attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
  245.                 attacker->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET;
  246.                 attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
  247.  
  248.                 // add the sprite over the player's head
  249.                 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET );
  250.                 attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
  251.                 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
  252.  
  253.                 // also play humiliation on target
  254.                 self->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET;
  255.                 self->client->ps.persistant[PERS_REWARD_COUNT]++;
  256.             }
  257.  
  258.             // check for two kills in a short amount of time
  259.             // if this is close enough to the last kill, give a reward sound
  260.             if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
  261.                 attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
  262.                 attacker->client->ps.persistant[PERS_REWARD] = REWARD_EXCELLENT;
  263.                 attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
  264.  
  265.                 // add the sprite over the player's head
  266.                 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET );
  267.                 attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
  268.                 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
  269.             }
  270.             attacker->client->lastKillTime = level.time;
  271.  
  272.         }
  273.     } else {
  274.         AddScore( self, -1 );
  275.     }
  276.  
  277.     // Add team bonuses
  278.     Team_FragBonuses(self, inflictor, attacker);
  279.  
  280.     // if client is in a nodrop area, don't drop anything (but return CTF flags!)
  281.     contents = trap_PointContents( self->r.currentOrigin, -1 );
  282.     if ( !( contents & CONTENTS_NODROP ) ) {
  283.         TossClientItems( self );
  284.     }
  285.     else {
  286.         if ( self->client->ps.powerups[PW_REDFLAG] ) {
  287.             Team_ReturnFlag(TEAM_RED);
  288.         }
  289.         else if ( self->client->ps.powerups[PW_BLUEFLAG] ) {
  290.             Team_ReturnFlag(TEAM_BLUE);
  291.         }
  292.     }
  293.  
  294.     Cmd_Score_f( self );        // show scores
  295.     // send updated scores to any clients that are following this one,
  296.     // or they would get stale scoreboards
  297.     for ( i = 0 ; i < level.maxclients ; i++ ) {
  298.         gclient_t    *client;
  299.  
  300.         client = &level.clients[i];
  301.         if ( client->pers.connected != CON_CONNECTED ) {
  302.             continue;
  303.         }
  304.         if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
  305.             continue;
  306.         }
  307.         if ( client->sess.spectatorClient == self->s.number ) {
  308.             Cmd_Score_f( g_entities + i );
  309.         }
  310.     }
  311.  
  312.     self->takedamage = qtrue;    // can still be gibbed
  313.  
  314.     self->s.weapon = WP_NONE;
  315.     self->s.powerups = 0;
  316.     self->r.contents = CONTENTS_CORPSE;
  317.  
  318.     self->s.angles[0] = 0;
  319.     self->s.angles[2] = 0;
  320.     LookAtKiller (self, inflictor, attacker);
  321.  
  322.     VectorCopy( self->s.angles, self->client->ps.viewangles );
  323.  
  324.     self->s.loopSound = 0;
  325.  
  326.     self->r.maxs[2] = -8;
  327.  
  328.     // don't allow respawn until the death anim is done
  329.     // g_forcerespawn may force spawning at some later time
  330.     self->client->respawnTime = level.time + 1700;
  331.  
  332.     // remove powerups
  333.     memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
  334.  
  335.     // never gib in a nodrop
  336.     if ( self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer ) {
  337.         // gib death
  338.         GibEntity( self, killer );
  339.     } else {
  340.         // normal death
  341.         static int i;
  342.  
  343.         switch ( i ) {
  344.         case 0:
  345.             anim = BOTH_DEATH1;
  346.             break;
  347.         case 1:
  348.             anim = BOTH_DEATH2;
  349.             break;
  350.         case 2:
  351.         default:
  352.             anim = BOTH_DEATH3;
  353.             break;
  354.         }
  355.  
  356.         // for the no-blood option, we need to prevent the health
  357.         // from going to gib level
  358.         if ( self->health <= GIB_HEALTH ) {
  359.             self->health = GIB_HEALTH+1;
  360.         }
  361.  
  362.         self->client->ps.legsAnim = 
  363.             ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
  364.         self->client->ps.torsoAnim = 
  365.             ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
  366.  
  367.         G_AddEvent( self, EV_DEATH1 + 1, killer );
  368.  
  369.         // the body can still be gibbed
  370.         self->die = body_die;
  371.  
  372.         // globally cycle through the different death animations
  373.         i = ( i + 1 ) % 3;
  374.     }
  375.  
  376.     trap_LinkEntity (self);
  377. }
  378.  
  379.  
  380. /*
  381. ================
  382. CheckArmor
  383. ================
  384. */
  385. int CheckArmor (gentity_t *ent, int damage, int dflags)
  386. {
  387.     gclient_t    *client;
  388.     int            save;
  389.     int            count;
  390.  
  391.     if (!damage)
  392.         return 0;
  393.  
  394.     client = ent->client;
  395.  
  396.     if (!client)
  397.         return 0;
  398.  
  399.     if (dflags & DAMAGE_NO_ARMOR)
  400.         return 0;
  401.  
  402.     // armor
  403.     count = client->ps.stats[STAT_ARMOR];
  404.     save = ceil( damage * ARMOR_PROTECTION );
  405.     if (save >= count)
  406.         save = count;
  407.  
  408.     if (!save)
  409.         return 0;
  410.  
  411.     client->ps.stats[STAT_ARMOR] -= save;
  412.  
  413.     return save;
  414. }
  415.  
  416.  
  417. /*
  418. ============
  419. T_Damage
  420.  
  421. targ        entity that is being damaged
  422. inflictor    entity that is causing the damage
  423. attacker    entity that caused the inflictor to damage targ
  424.     example: targ=monster, inflictor=rocket, attacker=player
  425.  
  426. dir            direction of the attack for knockback
  427. point        point at which the damage is being inflicted, used for headshots
  428. damage        amount of damage being inflicted
  429. knockback    force to be applied against targ as a result of the damage
  430.  
  431. inflictor, attacker, dir, and point can be NULL for environmental effects
  432.  
  433. dflags        these flags are used to control how T_Damage works
  434.     DAMAGE_RADIUS            damage was indirect (from a nearby explosion)
  435.     DAMAGE_NO_ARMOR            armor does not protect from this damage
  436.     DAMAGE_NO_KNOCKBACK        do not affect velocity, just view angles
  437.     DAMAGE_NO_PROTECTION    kills godmode, armor, everything
  438. ============
  439. */
  440.  
  441. void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
  442.                vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
  443.     gclient_t    *client;
  444.     int            take;
  445.     int            save;
  446.     int            asave;
  447.     int            knockback;
  448.  
  449.     if (!targ->takedamage) {
  450.         return;
  451.     }
  452.  
  453.     // the intermission has allready been qualified for, so don't
  454.     // allow any extra scoring
  455.     if ( level.intermissionQueued ) {
  456.         return;
  457.     }
  458.  
  459.     if ( !inflictor ) {
  460.         inflictor = &g_entities[ENTITYNUM_WORLD];
  461.     }
  462.     if ( !attacker ) {
  463.         attacker = &g_entities[ENTITYNUM_WORLD];
  464.     }
  465.  
  466.     // shootable doors / buttons don't actually have any health
  467.     if ( targ->s.eType == ET_MOVER ) {
  468.         if ( targ->use && targ->moverState == MOVER_POS1 ) {
  469.             targ->use( targ, inflictor, attacker );
  470.         }
  471.         return;
  472.     }
  473.  
  474.     // reduce damage by the attacker's handicap value
  475.     // unless they are rocket jumping
  476.     if ( attacker->client && attacker != targ ) {
  477.         damage = damage * attacker->client->ps.stats[STAT_MAX_HEALTH] / 100;
  478.     }
  479.  
  480.     client = targ->client;
  481.  
  482.     if ( client ) {
  483.         if ( client->noclip ) {
  484.             return;
  485.         }
  486.     }
  487.  
  488.     if ( !dir ) {
  489.         dflags |= DAMAGE_NO_KNOCKBACK;
  490.     } else {
  491.         VectorNormalize(dir);
  492.     }
  493.  
  494.     knockback = damage;
  495.     if ( knockback > 200 ) {
  496.         knockback = 200;
  497.     }
  498.     if ( targ->flags & FL_NO_KNOCKBACK ) {
  499.         knockback = 0;
  500.     }
  501.     if ( dflags & DAMAGE_NO_KNOCKBACK ) {
  502.         knockback = 0;
  503.     }
  504.  
  505.     // figure momentum add, even if the damage won't be taken
  506.     if ( knockback && targ->client ) {
  507.         vec3_t    kvel;
  508.         float    mass;
  509.  
  510.         mass = 200;
  511.  
  512.         VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
  513.         VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
  514.  
  515.         // set the timer so that the other client can't cancel
  516.         // out the movement immediately
  517.         if ( !targ->client->ps.pm_time ) {
  518.             int        t;
  519.  
  520.             t = knockback * 2;
  521.             if ( t < 50 ) {
  522.                 t = 50;
  523.             }
  524.             if ( t > 200 ) {
  525.                 t = 200;
  526.             }
  527.             targ->client->ps.pm_time = t;
  528.             targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
  529.         }
  530.     }
  531.  
  532.     // check for completely getting out of the damage
  533.     if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
  534.  
  535.         // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
  536.         // if the attacker was on the same team
  537.         if ( targ != attacker && OnSameTeam (targ, attacker)  ) {
  538.             if ( !g_friendlyFire.integer ) {
  539.                 return;
  540.             }
  541.         }
  542.  
  543.         // check for godmode
  544.         if ( targ->flags & FL_GODMODE ) {
  545.             return;
  546.         }
  547.     }
  548.  
  549.     // battlesuit protects from all radius damage (but takes knockback)
  550.     // and protects 50% against all damage
  551.     if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
  552.         G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
  553.         if ( dflags & DAMAGE_RADIUS ) {
  554.             return;
  555.         }
  556.         damage *= 0.5;
  557.     }
  558.  
  559.     // add to the attacker's hit counter
  560.     if ( attacker->client && targ != attacker && targ->health > 0 ) {
  561.         if ( OnSameTeam( targ, attacker ) ) {
  562.             attacker->client->ps.persistant[PERS_HITS] -= damage;
  563.         } else {
  564.             attacker->client->ps.persistant[PERS_HITS] += damage;
  565.         }
  566.     }
  567.  
  568.     // always give half damage if hurting self
  569.     // calculated after knockback, so rocket jumping works
  570.     if ( targ == attacker) {
  571.         damage *= 0.5;
  572.     }
  573.  
  574.     if ( damage < 1 ) {
  575.         damage = 1;
  576.     }
  577.     take = damage;
  578.     save = 0;
  579.  
  580.     // save some from armor
  581.     asave = CheckArmor (targ, take, dflags);
  582.     take -= asave;
  583.  
  584.     if ( g_debugDamage.integer ) {
  585.         G_Printf( "client:%i health:%i damage:%i armor:%i\n", targ->s.number,
  586.             targ->health, take, asave );
  587.     }
  588.  
  589.     // add to the damage inflicted on a player this frame
  590.     // the total will be turned into screen blends and view angle kicks
  591.     // at the end of the frame
  592.     if ( client ) {
  593.         if ( attacker ) {
  594.             client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
  595.         } else {
  596.             client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
  597.         }
  598.         client->damage_armor += asave;
  599.         client->damage_blood += take;
  600.         client->damage_knockback += knockback;
  601.         if ( dir ) {
  602.             VectorCopy ( dir, client->damage_from );
  603.             client->damage_fromWorld = qfalse;
  604.         } else {
  605.             VectorCopy ( targ->r.currentOrigin, client->damage_from );
  606.             client->damage_fromWorld = qtrue;
  607.         }
  608.     }
  609.  
  610.     // See if it's the player hurting the emeny flag carrier
  611.     Team_CheckHurtCarrier(targ, attacker);
  612.  
  613.     if (targ->client) {
  614.         // set the last client who damaged the target
  615.         targ->client->lasthurt_client = attacker->s.number;
  616.         targ->client->lasthurt_mod = mod;
  617.     }
  618.  
  619.     // do the damage
  620.     if (take) {
  621.         targ->health = targ->health - take;
  622.         if ( targ->client ) {
  623.             targ->client->ps.stats[STAT_HEALTH] = targ->health;
  624.         }
  625.             
  626.         if ( targ->health <= 0 ) {
  627.             if ( client )
  628.                 targ->flags |= FL_NO_KNOCKBACK;
  629.  
  630.             if (targ->health < -999)
  631.                 targ->health = -999;
  632.  
  633.             targ->enemy = attacker;
  634.             targ->die (targ, inflictor, attacker, take, mod);
  635.             return;
  636.         } else if ( targ->pain ) {
  637.             targ->pain (targ, attacker, take);
  638.         }
  639.     }
  640.  
  641. }
  642.  
  643.  
  644. /*
  645. ============
  646. CanDamage
  647.  
  648. Returns qtrue if the inflictor can directly damage the target.  Used for
  649. explosions and melee attacks.
  650. ============
  651. */
  652. qboolean CanDamage (gentity_t *targ, vec3_t origin) {
  653.     vec3_t    dest;
  654.     trace_t    tr;
  655.     vec3_t    midpoint;
  656.  
  657.     // use the midpoint of the bounds instead of the origin, because
  658.     // bmodels may have their origin is 0,0,0
  659.     VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
  660.     VectorScale (midpoint, 0.5, midpoint);
  661.  
  662.     VectorCopy (midpoint, dest);
  663.     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
  664.     if (tr.fraction == 1.0)
  665.         return qtrue;
  666.  
  667.     // this should probably check in the plane of projection, 
  668.     // rather than in world coordinate, and also include Z
  669.     VectorCopy (midpoint, dest);
  670.     dest[0] += 15.0;
  671.     dest[1] += 15.0;
  672.     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
  673.     if (tr.fraction == 1.0)
  674.         return qtrue;
  675.  
  676.     VectorCopy (midpoint, dest);
  677.     dest[0] += 15.0;
  678.     dest[1] -= 15.0;
  679.     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
  680.     if (tr.fraction == 1.0)
  681.         return qtrue;
  682.  
  683.     VectorCopy (midpoint, dest);
  684.     dest[0] -= 15.0;
  685.     dest[1] += 15.0;
  686.     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
  687.     if (tr.fraction == 1.0)
  688.         return qtrue;
  689.  
  690.     VectorCopy (midpoint, dest);
  691.     dest[0] -= 15.0;
  692.     dest[1] -= 15.0;
  693.     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
  694.     if (tr.fraction == 1.0)
  695.         return qtrue;
  696.  
  697.  
  698.     return qfalse;
  699. }
  700.  
  701.  
  702. /*
  703. ============
  704. G_RadiusDamage
  705. ============
  706. */
  707. qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
  708.                      gentity_t *ignore, int mod) {
  709.     float        points, dist;
  710.     gentity_t    *ent;
  711.     int            entityList[MAX_GENTITIES];
  712.     int            numListedEntities;
  713.     vec3_t        mins, maxs;
  714.     vec3_t        v;
  715.     vec3_t        dir;
  716.     int            i, e;
  717.     qboolean    hitClient = qfalse;
  718.  
  719.     if ( radius < 1 ) {
  720.         radius = 1;
  721.     }
  722.  
  723.     for ( i = 0 ; i < 3 ; i++ ) {
  724.         mins[i] = origin[i] - radius;
  725.         maxs[i] = origin[i] + radius;
  726.     }
  727.  
  728.     numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
  729.  
  730.     for ( e = 0 ; e < numListedEntities ; e++ ) {
  731.         ent = &g_entities[entityList[ e ]];
  732.  
  733.         if (ent == ignore)
  734.             continue;
  735.         if (!ent->takedamage)
  736.             continue;
  737.  
  738.         // find the distance from the edge of the bounding box
  739.         for ( i = 0 ; i < 3 ; i++ ) {
  740.             if ( origin[i] < ent->r.absmin[i] ) {
  741.                 v[i] = ent->r.absmin[i] - origin[i];
  742.             } else if ( origin[i] > ent->r.absmax[i] ) {
  743.                 v[i] = origin[i] - ent->r.absmax[i];
  744.             } else {
  745.                 v[i] = 0;
  746.             }
  747.         }
  748.  
  749.         dist = VectorLength( v );
  750.         if ( dist >= radius ) {
  751.             continue;
  752.         }
  753.  
  754.         points = damage * ( 1.0 - dist / radius );
  755.  
  756.         if( CanDamage (ent, origin) ) {
  757.             if( LogAccuracyHit( ent, attacker ) ) {
  758.                 hitClient = qtrue;
  759.             }
  760.             VectorSubtract (ent->r.currentOrigin, origin, dir);
  761.             // push the center of mass higher than the origin so players
  762.             // get knocked into the air more
  763.             dir[2] += 24;
  764.             G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
  765.         }
  766.     }
  767.  
  768.     return hitClient;
  769. }
  770.